<?php

declare(strict_types=1);

namespace app\service\user;

use InvalidArgumentException;
use think\facade\Cache;

class SmsCodeService
{
    private const CACHE_PREFIX = 'sms_code:';
    private const DEFAULT_TTL = 300; // 5 分钟

    public const SCENE_BIND = 'bind_phone';
    public const SCENE_LOGIN = 'login';
    public const SCENE_REGISTER = 'register';

    public function sendCode(string $scene, string $phone): string
    {
        $scene = $this->normalizeScene($scene);
        $this->assertPhone($phone);

        $mockCode = config('sms.mock_code');
        if (is_string($mockCode) && $mockCode !== '') {
            $code = $mockCode;
        } else {
            $code = (string) random_int(100000, 999999);
        }

        $this->storeCode($scene, $phone, $code, self::DEFAULT_TTL);

        // TODO: 集成真实短信网关，当前仅生成验证码
        return $code;
    }

    public function verifyCode(string $scene, string $phone, string $code, bool $consume = true): bool
    {
        $scene = $this->normalizeScene($scene);
        $this->assertPhone($phone);
        $code = trim($code);

        if ($code === '') {
            return false;
        }

        $cacheKey = $this->buildCacheKey($scene, $phone);
        $payload = Cache::get($cacheKey);
        if (!$payload || !isset($payload['code'])) {
            return false;
        }

        $valid = hash_equals((string) $payload['code'], $code);
        if ($valid && $consume) {
            Cache::delete($cacheKey);
        }

        return $valid;
    }

    public function invalidate(string $scene, string $phone): void
    {
        Cache::delete($this->buildCacheKey($scene, $phone));
    }

    private function storeCode(string $scene, string $phone, string $code, int $ttl): void
    {
        $payload = [
            'code' => $code,
            'expires_at' => time() + $ttl,
        ];
        Cache::set($this->buildCacheKey($scene, $phone), $payload, $ttl);
    }

    private function buildCacheKey(string $scene, string $phone): string
    {
        return self::CACHE_PREFIX . $scene . ':' . $phone;
    }

    private function normalizeScene(string $scene): string
    {
        $scene = trim(strtolower($scene));
        return match ($scene) {
            'bind', 'bind_phone', 'bindphone' => self::SCENE_BIND,
            'login', 'signin' => self::SCENE_LOGIN,
            'register', 'signup', 'reg' => self::SCENE_REGISTER,
            default => throw new InvalidArgumentException('不支持的验证码场景'),
        };
    }

    private function assertPhone(string $phone): void
    {
        if (!preg_match('/^1\d{10}$/', $phone)) {
            throw new InvalidArgumentException('手机号格式不正确');
        }
    }
}
